#include "MainWindow.h"
extern "C" {
#include <cpuid.h>
#include <tree.h>
}
#include "ui_MainWindow.h"

#include <QtGui/QGridLayout>
#include <QtGui/QGroupBox>
#include <QtGui/QFrame>

QTreeWidgetItem* addToplevelAsText(QTreeWidget *tree, const QString &text) {
    QTreeWidgetItem *toplItem = new QTreeWidgetItem;
    toplItem->setText(0, text);
    tree->insertTopLevelItem(tree->topLevelItemCount(), toplItem);
    
    return toplItem;
}

QTreeWidgetItem *addChildAsText(QTreeWidgetItem *parent, const QString &text) {
    QTreeWidgetItem *child = new QTreeWidgetItem;
    child->setText(0, text);
    parent->addChild(child);
    
    return child;
}

class LikVisBoxGrid {
public:
    explicit LikVisBoxGrid(QWidget *parent, int rowCount, int colCount)
        :mParent(parent),
        mRowCount(rowCount),
        mColCount(colCount)
    {
    }
    void addBox(int row, int col, int offset, const QString &text)
    {
        return addJoinedBox(row, col, 1, offset, text);
    }

    void addJoinedBox(int row, int colBegin, int colCount, int offset, const QString &text)
    {
        int rowCount = 1;

        QGroupBox *groupBox = new QGroupBox(mParent);

        offset *= 20;

        groupBox->setGeometry(
                offset + (colBegin*mParent->geometry().width())/mColCount,
                offset + (row*mParent->geometry().height())/mRowCount,
                -2*offset + (colCount*mParent->geometry().width())/mColCount,
                -2*offset + (rowCount*mParent->geometry().height())/mRowCount);
        QFrame *frame = new QFrame(groupBox);
        frame->setGeometry(
                0,
                0,
                groupBox->geometry().width(),
                groupBox->geometry().height());
        frame->setFrameShape(QFrame::StyledPanel);
        frame->setFrameShadow(QFrame::Raised);

        groupBox->setTitle(text);
        groupBox->setAlignment(Qt::AlignHCenter);

        //groupBox->setStyleSheet("background-color: qlineargradient(spread:pad, x1:0, y1:0, x2:0, y2:1, stop:0 rgba(255, 255, 255, 255), stop:1 rgba(50, 50, 50, 255));");
    }

private:
    QWidget *mParent;
    int mRowCount, mColCount;
};

//class

MainWindow::MainWindow(QWidget *parent) :
	QMainWindow(parent),
	ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    cpuid_init();

	// CPU information
	{
		this->ui->cpuTypeLabel->setText(cpuid_info.name);
		this->ui->cpuClockLabel->setText(QString().sprintf("%3.2f GHz", (float) cpuid_info.clock * 1.E-09));
	}

    cpuid_initTopology();
    cpuid_initCacheTopology();

    // Hardwarware Thread Topology
    {
        this->ui->threadTopologyNumSocketsLabel->setText(QString().sprintf("%u", cpuid_topology.numSockets));
        this->ui->threadTopologyNumCoresPerSocket->setText(QString().sprintf("%u", cpuid_topology.numCoresPerSocket));
        this->ui->threadTopologyNumThreadsPerCore->setText(QString().sprintf("%u", cpuid_topology.numThreadsPerCore));
    }

    // Thread topology table
    for (unsigned int i=0; i< cpuid_topology.numHWThreads; i++)
    {
        this->ui->threadTopologyTable->insertRow(i);

        this->ui->threadTopologyTable->setItem(i, 0, new QTableWidgetItem());
        this->ui->threadTopologyTable->setItem(i, 1, new QTableWidgetItem());
        this->ui->threadTopologyTable->setItem(i, 2, new QTableWidgetItem());
        this->ui->threadTopologyTable->setItem(i, 3, new QTableWidgetItem());


        this->ui->threadTopologyTable->item(i, 0)->setText(QString().sprintf("%d", i));
        this->ui->threadTopologyTable->item(i, 1)->setText(QString().sprintf("%u", cpuid_topology.threadPool[i].threadId));
        this->ui->threadTopologyTable->item(i, 2)->setText(QString().sprintf("%u", cpuid_topology.threadPool[i].coreId));
        this->ui->threadTopologyTable->item(i, 3)->setText(QString().sprintf("%u", cpuid_topology.threadPool[i].packageId));

    }
    // Threads per socket
    {
        TreeNode* socketNode = tree_getChildNode(cpuid_topology.topologyTree);
        while (socketNode != NULL)
        {
            QTreeWidgetItem *socketItem =
                    addToplevelAsText(this->ui->threadTopologyTree,  QString().sprintf("Socket %d",socketNode->id));

            TreeNode* coreNode = tree_getChildNode(socketNode);

            while (coreNode != NULL)
            {
                TreeNode* threadNode = tree_getChildNode(coreNode);

                while (threadNode != NULL)
                {
                    addChildAsText(socketItem, QString().sprintf("Thread %d ",threadNode->id));

                    threadNode = tree_getNextNode(threadNode);
                }
                coreNode = tree_getNextNode(coreNode);
            }
            socketNode = tree_getNextNode(socketNode);
        }
    }
    // Cache topology, tree
    for (unsigned int i=0; i< cpuid_topology.numCacheLevels; i++)
    {
        if (cpuid_topology.cacheLevels[i].type != INSTRUCTIONCACHE)
        {
            QTreeWidgetItem *cacheItem =
                    addToplevelAsText(this->ui->cacheTopologyTree, QString().sprintf("Level  %d",cpuid_topology.cacheLevels[i].level));

            if (cpuid_topology.cacheLevels[i].size < 1048576) {
                addChildAsText(cacheItem, QString().sprintf("Size %d kB", cpuid_topology.cacheLevels[i].size/1024));
            } else {
                addChildAsText(cacheItem, QString().sprintf("Size %d MB", cpuid_topology.cacheLevels[i].size/1048576));
            }

            {
                QTreeWidgetItem *advancedItem = addChildAsText(cacheItem, "Advanced");

                switch (cpuid_topology.cacheLevels[i].type) {
                    case DATACACHE:
                        addChildAsText(advancedItem, "Type: Data cache");
                        break;

                    case INSTRUCTIONCACHE:
                        addChildAsText(advancedItem, "Type: Instruction cache");
                        break;

                    case UNIFIEDCACHE:
                        addChildAsText(advancedItem, "Type: Unified cache");
                        break;
                    default:
                        addChildAsText(advancedItem, "Type: Unknown");
                }

                addChildAsText(advancedItem, QString().sprintf("Associativity: %d",
                                                               cpuid_topology.cacheLevels[i].associativity));

                addChildAsText(advancedItem, QString().sprintf("Number of sets: %d",
                                                               cpuid_topology.cacheLevels[i].sets));

                addChildAsText(advancedItem, QString().sprintf("Cache line size: %d",
                                                               cpuid_topology.cacheLevels[i].lineSize));

                if(cpuid_topology.cacheLevels[i].inclusive)
                {
                    addChildAsText(advancedItem, "Non Inclusive cache");
                }
                else
                {
                    addChildAsText(advancedItem, "Inclusive cache");
                }

                addChildAsText(advancedItem, QString().sprintf("Shared among %d threads",
                                                               cpuid_topology.cacheLevels[i].threads));
            }

            // Cache groups
            {
                QTreeWidgetItem *cacheGroupsItem = addChildAsText(cacheItem, "Cache groups");
                QTreeWidgetItem *cacheGroupItem = NULL;
                unsigned int groupCounter = 0;

                // number of threads that can be but into current cache group until it is full
                unsigned int remainingThreadsInCache = 0;
                TreeNode* socketNode = tree_getChildNode(cpuid_topology.topologyTree);
                while (socketNode != NULL)
                {
                    TreeNode* coreNode = tree_getChildNode(socketNode);

                    while (coreNode != NULL)
                    {
                        TreeNode* threadNode = tree_getChildNode(coreNode);

                        while (threadNode != NULL)
                        {
                            // open a new cache group if the current one is full
                            if (remainingThreadsInCache == 0) {
                                cacheGroupItem = addChildAsText(cacheGroupsItem, QString().sprintf("Group %d", groupCounter));
                                groupCounter++;
                                remainingThreadsInCache = cpuid_topology.cacheLevels[i].threads;
                            }

                            addChildAsText(cacheGroupItem, QString().sprintf("Thread %d ",threadNode->id));

                            threadNode = tree_getNextNode(threadNode);
                            
                            remainingThreadsInCache--;
                        }
                        coreNode = tree_getNextNode(coreNode);
                    }
                    socketNode = tree_getNextNode(socketNode);
                }
            }
        }
    }
    // Cache Topology, graphical
    {
        LikVisBoxGrid socketsBoxes(this->ui->cacheTopologyVis, 1, cpuid_topology.numSockets);
        unsigned int currentSocketId = 0;
        TreeNode* socketNode = tree_getChildNode(cpuid_topology.topologyTree);
        while (socketNode != NULL)
        {
            socketsBoxes.addBox(0, currentSocketId, 0, QString().sprintf("Socket %d", socketNode->id));
            currentSocketId++;

            LikVisBoxGrid socketBoxes(NULL, 0, 0);
            /* Allocate without instruction cache */
            if ( cpuid_info.family == P6_FAMILY)
            {
                socketBoxes = LikVisBoxGrid(this->ui->cacheTopologyVis,
                              cpuid_topology.numCacheLevels,
                              cpuid_topology.numCoresPerSocket);
            }
            else
            {
                socketBoxes = LikVisBoxGrid(this->ui->cacheTopologyVis,
                        cpuid_topology.numCacheLevels+1,
                        cpuid_topology.numCoresPerSocket);
            }

            unsigned int j=0;
            TreeNode* coreNode = tree_getChildNode(socketNode);

            /* add threads */
            while (coreNode != NULL)
            {
                TreeNode* threadNode = tree_getChildNode(coreNode);
                unsigned int tmp = 0;
                QString boxLabel;
                while (threadNode != NULL)
                {
                    if (tmp > 0)
                    {
                        boxLabel = boxLabel + QString().sprintf("  %d", threadNode->id);
                    }
                    else
                    {
                        boxLabel.sprintf("Core %d",threadNode->id);
                    }
                    tmp++;
                    threadNode = tree_getNextNode(threadNode);
                }
                socketBoxes.addBox(0, j, 1, boxLabel);
                j++;
                coreNode = tree_getNextNode(coreNode);
            }

            /* add caches */
            {
                int columnCursor=0;
                int lineCursor=1;
                int sharedCores;
                int numCachesPerLevel;
                int cacheWidth;

                for (unsigned int i=0; i< cpuid_topology.numCacheLevels; i++)
                {
                    sharedCores = cpuid_topology.cacheLevels[i].threads /
                        cpuid_topology.numThreadsPerCore;

                    if (cpuid_topology.cacheLevels[i].type != INSTRUCTIONCACHE)
                    {
                        if (sharedCores > cpuid_topology.numCoresPerSocket)
                        {
                            numCachesPerLevel = 1;
                        }
                        else
                        {
                            numCachesPerLevel =
                                cpuid_topology.numCoresPerSocket/sharedCores;
                        }

                        columnCursor=0;
                        for (j=0; j< numCachesPerLevel; j++)
                        {
                            QString boxLabel;
                            if (cpuid_topology.cacheLevels[i].size < 1048576)
                            {
                                boxLabel.sprintf("L%d Cache: %d kB",
                                        cpuid_topology.cacheLevels[i].level, cpuid_topology.cacheLevels[i].size/1024);
                            }
                            else
                            {
                                boxLabel.sprintf("L%d Cache: %d MB",
                                        cpuid_topology.cacheLevels[i].level, cpuid_topology.cacheLevels[i].size/1048576);
                            }

                            if (sharedCores > 1)
                            {
                                if (sharedCores > cpuid_topology.numCoresPerSocket)
                                {
                                    cacheWidth = cpuid_topology.numCoresPerSocket;
                                }
                                else
                                {
                                    cacheWidth = sharedCores;
                                }
                                socketBoxes.addJoinedBox(
                                        lineCursor,
                                        columnCursor,
                                        cacheWidth,
                                        1,
                                        boxLabel);

                                columnCursor += sharedCores;
                            }
                            else
                            {
                                socketBoxes.addBox(
                                        lineCursor,
                                        columnCursor,
                                        1,
                                        boxLabel);

                                columnCursor++;
                            }

                        }
                        lineCursor++;
                    }
                }
            }

            socketNode = tree_getNextNode(socketNode);
        }
    }
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::changeEvent(QEvent *e)
{
    QMainWindow::changeEvent(e);
    switch (e->type()) {
    case QEvent::LanguageChange:
        ui->retranslateUi(this);
        break;
    default:
        break;
    }
}
